/*
* Copyright 2012 Shared Learning Collaborative, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.slc.sli.dashboard.web.util;
import java.lang.annotation.Annotation;
import javax.validation.Valid;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.stereotype.Component;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
/**
* Fix for 3.0 spring to validate @requestbody and @requestparam
* Aspect provides validation for requestmapping annotated methods since Spring 3.0 provides modelattribute validation only
* @author agrebneva
*
*/
@Aspect
@Component
@Scope(value = "singleton")
public class ControllerInputValidatorAspect {
private class DefaultStringValidatable {
@SuppressWarnings("unused")
@NoBadChars
private String validatableString;
public DefaultStringValidatable(String validatableString) {
this.validatableString = validatableString;
}
}
private Validator validator;
/**
* All methods annotation with RequestMapping
*/
@Pointcut("execution(@org.springframework.web.bind.annotation.RequestMapping * *(..))")
@SuppressWarnings("unused")
private void controllerMethodInvocation() {
}
/**
* Around for pointcut defined by controllerMethodInvocation
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("controllerMethodInvocation()")
public Object aroundController(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Annotation[][] annotations = methodSignature.getMethod().getParameterAnnotations();
String[] paramNames = methodSignature.getParameterNames();
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
if (checkAnnotations(annotations[i])) {
validateArg(args[i], paramNames[i]);
}
}
return joinPoint.proceed(args);
}
/**
* Find params to validate using annotations
* @param paramAnnotations - annotations for method args
* @return if marked to be validated
*/
private boolean checkAnnotations(Annotation[] paramAnnotations) {
boolean isValidate = false, isNonModelParam = false;
for (Annotation annotation : paramAnnotations) {
if (Valid.class.isInstance(annotation)) {
isValidate = true;
} else if (RequestParam.class.isInstance(annotation)
|| PathVariable.class.isInstance(annotation)
|| RequestBody.class.isInstance(annotation)) {
isNonModelParam = true;
}
}
return isValidate && isNonModelParam;
}
/**
* Validate param using param specific validator and validate all strings using a blacklist validator
* @param arg
* @param argName
*/
private void validateArg(Object arg, String argName) {
BindingResult result = new BeanPropertyBindingResult(arg, argName);
ValidationUtils.invokeValidator(getValidator(), arg, result);
// force string validation for bad chars
if (arg instanceof String) {
ValidationUtils.invokeValidator(getValidator(), new DefaultStringValidatable((String) arg), result);
}
if (result.hasErrors()) {
throw new HttpMessageConversionException("Invalid input parameter " + argName, new BindException(result));
}
}
@Autowired
public void setValidator(Validator validator) {
this.validator = validator;
}
public Validator getValidator() {
return validator;
}
}